﻿using Jacobi.Vst.Core;
using Jacobi.Vst.Framework;
using Jacobi.Vst.Framework.Plugin;
using Aleator.Core;
using Aleator.Helpers;
using System;
using System.Diagnostics;

namespace Aleator
{
	/// <summary>
	/// A dummy audio processor only used for the timing of midi processing.
	/// </summary>
	class AudioProcessor : VstPluginAudioProcessorBase, IVstPluginBypass
	{
		private const int AudioInputCount = 2;
		private const int AudioOutputCount = 2;
		private const int InitialTailSize = 0;

		public int IsPlaying;
		public static int Errors;
		public static int EventErrors;

		private static DateTime m_stopTime;
		private static bool m_dead;

		private Plugin _plugin;
		private MidiProcessor _midiProcessor;
		private VstTimeInfoFlags _defaultTimeInfoFlags;
		private IVstMidiProcessor _hostProcessor;
		private IVstHostSequencer _sequencer;

		/// <summary>
		/// Constructs a new instance.
		/// </summary>
		/// <param name="plugin">Must not be null.</param>
		public AudioProcessor(Plugin plugin)
		    : base(AudioInputCount, AudioOutputCount, InitialTailSize)
		{
			_plugin = plugin;
			_midiProcessor = plugin.GetInstance<MidiProcessor>();
			_timeInfo = new VstTimeInfo();

			_defaultTimeInfoFlags = VstTimeInfoFlags.ClockValid | VstTimeInfoFlags.TempoValid | VstTimeInfoFlags.TransportPlaying;
		}

		/// <inheritdoc />
		/// <remarks>This method is used to push midi events to the host.</remarks>
		public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels)
		{
			_timeInfo = null;

			if (_hostProcessor == null)
			{
				_hostProcessor = _plugin.Host.GetInstance<IVstMidiProcessor>();
			}

			if (_midiProcessor != null && _hostProcessor != null && _sequencer != null)
			{
				_timeInfo = _sequencer.GetTime(_defaultTimeInfoFlags);

				if (_midiProcessor.Events.Count == 0 && IsPlaying == 2 && !Session.Paused)
				{
					//Logger.Log(TraceEventType.Error, "The Aleator process has generated no events.");
					EventErrors++;
				}


				if (_midiProcessor.Events.Count > 0)
				{

					if (_hostProcessor != null && _midiProcessor != null && _midiProcessor.Events != null)
					{
						VstEventCollection events = null;

						//Try to retrieve events list
						try
						{
							events = _midiProcessor.Events;
						}
						catch (Exception e)
						{
							if (Errors > 20 && m_dead == false)
							{
								Logger.Log(TraceEventType.Error, "Aleaor process has died while attempting to get events from the MIDI processor. Please restart.");
								m_dead = true;

								throw new ApplicationException("The Aleator process has died.");
							}
							else
							{
								Logger.Log(TraceEventType.Warning, string.Format("{0} handled by Aleator audio processor at {1} while attempting to get events from the MIDI processor.", e.GetType().ToString(), DateTime.Now.ToString("HH:mm:ss.fff")));
								Errors++;
							}
						}

						//Try to process events
						try
						{
							_hostProcessor.Process(events);
						}
						catch (Exception e)
						{
							if (Errors > 20 && m_dead == false)
							{
								Logger.Log(TraceEventType.Error, "Host processor cannot process events. Please restart.");
								m_dead = true;

								throw new ApplicationException("The Aleator process has died.");
							}
							else
							{
								Logger.Log(TraceEventType.Warning, string.Format("{0} handled by Aleator audio processor at {1} when host process received events.", e.GetType().ToString(), DateTime.Now.ToString("HH:mm:ss.fff")));
								Errors++;
							}
						}

						//Try to clear the event list
						try
						{
							_midiProcessor.Events.Clear();
						}
						catch (Exception e)
						{
							if (Errors > 20 && m_dead == false)
							{
								Logger.Log(TraceEventType.Error, "Aleaor process has died while attempting to clear event list. Please restart.");
								m_dead = true;

								throw new ApplicationException("The Aleator process has died.");
							}
							else
							{
								Logger.Log(TraceEventType.Warning, string.Format("{0} handled by Aleator audio processor at {1} while attempting to clear event list.", e.GetType().ToString(), DateTime.Now.ToString("HH:mm:ss.fff")));
								Errors++;
							}
						}
					}

					m_stopTime = DateTime.MinValue;
				}
				else
				{
					TimeSpan session_ts = DateTime.Now - Session.StopTime;
					TimeSpan processor_ts = DateTime.Now - m_stopTime;

					//Suicide right here
					if (((session_ts.Minutes > 5 && Session.StopTime > DateTime.MinValue) || (processor_ts.Minutes > 5 && m_stopTime > DateTime.MinValue)) && m_dead == false)
					{
						Logger.Log(TraceEventType.Error, "The Aleator process has run for 5 minutes without generating events, please restart.");
						m_dead = true;
					}
					else if (m_stopTime == DateTime.MinValue)
						m_stopTime = DateTime.Now;
				}

				//Every so often getting information from DAW throws NullReferenceException. If this happens a ton of times in a row it usually means the Aleator process is dead.
				try
				{
					if (((int)_timeInfo.Flags & (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying) != IsPlaying)
					{
						IsPlaying = ((int)_timeInfo.Flags & (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying);
						_plugin.Editor.UIWrapper.Instance.ToggleButtons(IsPlaying);
					}
				}
				catch (NullReferenceException)
				{
					if (Errors > 20 && m_dead == false)
					{
						Logger.Log(TraceEventType.Error, "Aleaor process has died, please restart.");
						m_dead = true;

						throw new ApplicationException("The Aleator process can no longer retrieve information from the DAW, please restart.");
					}
					else
					{
						Logger.Log(TraceEventType.Warning, "NullReferenceException handled by Aleator audio processor at " + DateTime.Now.ToString("HH:mm:ss.fff"));
						Errors++;
					}
				}
			}
			else if (m_dead == false)
			{
				Logger.Log(TraceEventType.Error, "At least one of the objects necessary for processing is null, please restart.");
				m_dead = true;
			}
		}


		public bool Bypass { get; set; }

		private VstTimeInfo _timeInfo;
		/// <summary>
		/// Gets the current time info.
		/// </summary>
		/// <remarks>The Time Info is refreshed with each call to Process.</remarks>
		public VstTimeInfo TimeInfo
		{
			get
			{
				if (_sequencer == null && _plugin.Host != null)
				{
					_sequencer = _plugin.Host.GetInstance<IVstHostSequencer>();
				}

				if (_sequencer != null)
				{
					_timeInfo = _sequencer.GetTime(_defaultTimeInfoFlags);
				}

				return _timeInfo;
			}
		}

		private static int _scrollNumber;

		/// <summary>
		/// Number of positions to scroll the synth browser
		/// </summary>
		public static int ScrollNumber
		{
			get
			{
				return _scrollNumber;
			}
			set
			{
				_scrollNumber = value;
			}
		}
	}
}
